local SpanLayer = require("skywalking.span_layer")
local Util = require('skywalking.util')
local SegmentRef = require("skywalking.segment_ref")
local table = table

-- @segment:
-- local Span = {
--     span_id,
--     parent_span_id,
--     operation_name,
--     tags,
--     layer,
--     is_entry = false,
--     is_exit = false,
--     peer,
--     start_time,
--     end_time,
--     error_occurred = false,
--     component_id,
--     refs,
--     is_noop = false,
--     tracing_context,
-- }
local _M = {}

-- Tag相关的变量
_M.Tag_Constants = {
    TagURL              = "url",
    TagStatusCode       = "status_code",
    TagHTTPMethod       = "http.method",
    TagHTTPStatus       = "http.status",
    TagHTTPParams       = "http.params",
    TagHTTPBody         = "http.body",
    TagHTTPHost         = "http.host",
    TagHTTPResponseStatus    = "http.response.status",
    TagHTTPResponseBody      = "http.response.body",
    TagXForwardedFor    = "x-forwarded-for",
    TagXDeviceID        = "x-device-id",
    TagXRealIP          = "x-real-ip",
    TagXIsClientIP      = "x-is-client-ip",
    TagContentLength    = "content_length",
    TagContentType      = "content_type",
    TagXRequestID       = "x_request_id",
    TagUserAgent        = "user_agent",
    TagDBType           = "db.type",
    TagDBInstance       = "db.instance",
    TagDBStatement      = "db.statement",
    TagDBSqlName        = "db.sql.name",
    TagDBSqlParameters  = "db.sql.parameters",
    TagDBName           = "db.database",
    TagDBBindVariables  = "db.bind_vars",
    TagMQQueue          = "mq.queue",
    TagMQBroker         = "mq.broker",
    TagMQTopic          = "mq.topic",
    TagXUserID          = "x-user-id",
}

-- nginxComponentId
_M.ComponentId_Constants = {
    NginxComponentId = 6000,
    MysqlComponentId = 5,
    OracleComponentId = 6,
    RedisComponentId = 7,
    MmotanComponentId = 8,
    MongodbComponentId = 9,
    LuaComponentId = 6000,
    KafkaComponentId = 27,
    GRPCComponentId = 23,
    HttpClientComponentId = 2,
    ORACLEComponentId = 6,
    MongoDBComponentId = 9,
    SpringMVCComponentId = 14,
    RocketMQComponentId = 25,
    SQLiteComponentId = 31,
    KafkaProducerComponentId = 40,
    KafkaConsumerComponentId = 41,
    ElasticsearchComponentId = 47,
    HttpComponentId = 49,
    CanalComponentId = 54,
    ZookeeperComponentId = 58,
    InfluxDBComponentId = 89,
    HBaseComponentId = 94,
    ClickHouseComponentId = 120,
    SqlClientComponentId = 3003,
    PythonComponentId = 7000,
    GinComponentId = 5006,
    NsqClientComponentId = 26,
}

_M.DbTypeMap = {
    ["dbTypeRedis"] = "Redis",
    ["dbTypeMysql"] = "Mysql",
    ["dbTypeMemcache"] = "Memcache",
}

_M.SpanLayer = SpanLayer

-- Create a default span.
--- @param tracing_context
--- @param parent_span
function _M.new(operationName, tracing_context, parent_span)
    local span = _M.newNooP()
    span.is_noop = false

    span.operation_name = operationName
    span.span_id = tracing_context.nextSpanID(tracing_context)

    if parent_span == nil then
        -- As the root span, the parent span id is -1
        span.parent_span_id = -1
    else
        span.parent_span_id = parent_span.span_id
    end

    tracing_context.addActive(tracing_context, span)
    span.refs = {}
    span.tracing_context = tracing_context

    return setmetatable(span, {  __index = _M })
end

--- 是否是noop
function _M.isNoop(span)
    if span.is_noop == true then
        return true
    end

    return false
end

--- 是否需要被采样
function _M.isSample(span)
    if span.is_noop == true then
        return false
    end

    if span.tracing_context and span.tracing_context.sample == 1 then
        return true
    end

    return false
end

function _M.newNooP()
    local obj = Util.tablepool_fetch()
    obj.layer = SpanLayer.NONE
    obj.is_entry = false
    obj.is_exit = false
    obj.error_occurred = false
    obj.is_noop = true
    return setmetatable(obj, _M)
end

---- All belowing are instance methods

-- Set start time explicitly
function _M.start(span, startTime)
    if span.is_noop then
        return
    end

    span.start_time = startTime

    return
end

function _M.finishWithDuration(span, duration)
    if span.is_noop then
        return
    end

    _M.finish(span, span.start_time + duration)

    return
end

-- @param endTime, optional.
function _M.finish(span, endTime)
    if span.is_noop then
        return
    end

    if endTime == nil then
        span.end_time = Util.timestamp()
    else
        span.end_time = endTime
    end
    span.tracing_context.finishSpan(span.tracing_context, span)

    return
end

function _M.setComponentId(span, componentId)
    if span.is_noop then
        return
    end
    span.component_id = componentId

    return
end

function _M.setLayer(span, span_layer)
    if span.is_noop then
        return
    end
    span.layer = span_layer

    return
end

function _M.errorOccurred(span)
    if span.is_noop then
        return
    end
    span.error_occurred = true

    return
end

function _M.tag(span, tagKey, tagValue)
    if span.is_noop or not span.isSample(span) then
        return
    end

    if span.tags == nil then
        span.tags = {}
    end

    local tag = {key = tagKey, value = tagValue}
    span.tags[#span.tags + 1] = tag

    return
end

-- @param ... keyValuePairs is a typical {key="xxx", value="xxx"}
function _M.log(span, timestamp, ...)
    if span.is_noop or not span.isSample(span) then
        return
    end

    if span.logs == nil then
        span.logs = Util.tablepool_fetch()
    end

    local logEntity = Util.tablepool_fetch()
    logEntity.time = timestamp
    logEntity.data = {...}

    table.insert(span.logs, logEntity)
    return
end

--- 记录错误日志
function _M.error(span, timestamp, ...)
    if span.is_noop or not span.isSample(span) then
        return
    end

    span.error_occurred = true
    _M.log(span, timestamp, ...)
end

function _M.setOperationName(span, operation_name)
    span.operation_name = operation_name
end

-- Return SpanProtocol
function _M.transform(span)
    local spanBuilder = Util.tablepool_fetch("sw_spanBuilder", 0, 32)
    spanBuilder.spanId = span.span_id
    spanBuilder.parentSpanId = span.parent_span_id
    spanBuilder.startTime = span.start_time
    spanBuilder.endTime = span.end_time
    -- Array of RefProtocol
    if #span.refs > 0 then
        spanBuilder.refs = Util.tablepool_fetch("sw_spanBuilder_refs", 4, 0)
        for _, ref in ipairs(span.refs) do
            table.insert(spanBuilder.refs, SegmentRef.transform(ref))
        end
    end

    spanBuilder.operationName = span.operation_name
    spanBuilder.peer = span.peer
    if span.is_entry then
        spanBuilder.spanType = 'Entry'
    elseif span.is_exit then
        spanBuilder.spanType = 'Exit'
    else
        spanBuilder.spanType = 'Local'
    end
    if span.layer ~= SpanLayer.NONE then
        spanBuilder.spanLayer = span.layer.name
    end
    spanBuilder.componentId = span.component_id
    spanBuilder.isError = span.error_occurred

    spanBuilder.tags = span.tags
    spanBuilder.logs = span.logs
    
    local max_len = 800
    -- 正对log里面的bigkey，进行裁剪
    if spanBuilder.logs and next(spanBuilder.logs) then
        for _,v in pairs(spanBuilder.logs) do
            local data = v.data
            for _, info in pairs(data) do
                local value = info.value
                if value and type(value) == "string" and string.len(value) > max_len then
                    info.value = string.sub(value, 1, max_len)
                end
            end
        end
    end

    return spanBuilder
end

function _M.tracing_context(span)
    return span.tracing_context
end

return _M
